/**
 *  TextMash - simple IDE for Clojure
 *  
 *  Copyright (C) 2010 Aleksander Naszko
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package pl.olek.textmash.matching;

import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 
 * @author anaszko
 *
 */
public class Dictionary implements Serializable {

	private static Dictionary singleton;

	public static synchronized Dictionary getInstance() {
		if (singleton == null) {
			singleton = new Dictionary();
		}
		return singleton;
	}

	public synchronized static void save(ObjectOutputStream output) {
		try {
			output.writeObject(getInstance());
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public synchronized static void load(ObjectInputStream input) {
		try {
			singleton = (Dictionary) input.readObject();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private static final long serialVersionUID = 1L;

	final static int RESULT_LIMIT = 20;

	static class Cmp implements Comparator<String>, Serializable {
		private static final long serialVersionUID = 1L;
		boolean entering = true;

		@Override
		public int compare(String o1, String o2) {
			if (entering) {
				return o1.compareToIgnoreCase(o2);
			}
			return o1.regionMatches(true, 0, o2, 0, o2.length()) ? 0 : o1
					.compareToIgnoreCase(o2);
		}

		public void setMode(boolean entering) {
			this.entering = entering;
		}
	}

	Cmp cmp = new Cmp();

	TreeMap<String, Integer> index = new TreeMap<String, Integer>(cmp);

	TreeMap<String, TreeSet<String>> words = new TreeMap<String, TreeSet<String>>();

	public Dictionary() {

	}

	public synchronized void process(String file) {
		try {
			File fn = new File(file);
			FileInputStream fin = new FileInputStream(fn);
			byte[] data = new byte[(int) fn.length()];
			fin.read(data);
			process(file, new String(data));
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public synchronized void process(String file, String content) {
		try {
			TreeSet<String> list = words.get(file);
			if (list != null) {
				for (String word : list) {
					pure(word);
				}
				list.clear();
			} else {
				list = new TreeSet<String>();
				words.put(file, list);
			}
			Pattern pat = Pattern
					.compile("(^|\\P{L}+?)([\\p{L}][\\p{L}\\d_-]{2,49})");
			Matcher mat = pat.matcher(content);
			int pos = 0;
			while (mat.find(pos)) {
				String word = mat.group(2);
				if (list.add(word.toLowerCase())) {
					put(word);
				}
				pos = mat.end();
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public synchronized void clear() {
		index.clear();
		words.clear();
	}

	public synchronized void pure(String value) {
		cmp.setMode(true);
		Integer v = index.get(value);
		if (v != null) {
			if (v == 1) {
				index.remove(value);
			} else {
				index.put(value, v - 1);
			}
		}
	}

	public synchronized List<String> propose(String prefix, int limit) {
		ArrayList<String> results = new ArrayList<String>(limit);
		ArrayList<Entry<String, Integer>> entries = new ArrayList<Entry<String, Integer>>();
		for (Entry<String, Integer> e : get(prefix).entrySet()) {
			if (entries.size() >= limit) {
				break;
			}
			entries.add(e);
		}
		Collections.sort(entries, new Comparator<Entry<String, Integer>>() {

			@Override
			public int compare(Entry<String, Integer> o1,
					Entry<String, Integer> o2) {
				int r = o2.getValue() - o1.getValue();
				if (r == 0) {
					return o1.getKey().compareToIgnoreCase(o2.getKey());
				}
				return r;
			}
		});
		for (Entry<String, Integer> e : entries) {
			results.add(e.getKey());
		}
		return results;
	}

	public synchronized Map<String, Integer> get(String value) {
		cmp.setMode(false);
		final String keyValue = value;
		final Iterator<Entry<String, Integer>> it = index.tailMap(value, true)
				.entrySet().iterator();
		Iterator<Entry<String, Integer>> res = new Iterator<Entry<String, Integer>>() {
			Entry<String, Integer> entry;

			@Override
			public boolean hasNext() {
				if (it.hasNext()) {
					if (entry == null) {
						entry = it.next();
					}
					if (entry.getKey().regionMatches(true, 0, keyValue, 0,
							keyValue.length())) {
						return true;
					}
				}
				return false;
			}

			@Override
			public Entry<String, Integer> next() {
				Entry<String, Integer> result = entry;
				entry = null;
				return result;
			}

			@Override
			public void remove() {
				throw new UnsupportedOperationException();
			}

		};
		TreeMap<String, Integer> result = new TreeMap<String, Integer>(
				new Cmp());
		int limit = 0;
		while (res.hasNext() && limit++ < RESULT_LIMIT) {
			Entry<String, Integer> e = res.next();
			result.put(e.getKey(), e.getValue());
		}
		return result;
	}

	public synchronized void put(String value) {
		cmp.setMode(true);
		Integer v = index.get(value);
		if (v == null) {
			index.put(value, 1);
		} else {
			index.put(value, v + 1);
		}
	}

//	public static void main(String[] args) {
//
//		Dictionary dict = new Dictionary();
//
//		dict.process("/Users/aleksander/Documents/imiona dla kota.txt");
//		dict.process("/Users/aleksander/Documents/azzar.txt");
//		// dict.process("/Users/aleksander/Documents/imiona dla kota.txt");
//
//		// for (int i = 0; i < 100; ++i) {
//		dict.put("ambrozja"/* + i */);
//		dict.put("niedzielna" /* + i */);
//		dict.put("olek"/* + i */);
//		dict.put("abażur" /* + i */);
//		dict.put("nazko"/* + i */);
//		dict.put("olek"/* + i */);
//		dict.put("olesław" /* + i */);
//		dict.put("frendzel" /* + i */);
//		dict.put("olaek" /* + i */);
//		dict.put("zioło" /* + i */);
//		dict.put("nazkowiski" /* + i */);
//		dict.put("farfaleendzel" /* + i */);
//		// }
//		// dict.pure("olek");
//
//		// for (Entry<String, Integer> e : dict.get("f").entrySet()) {
//		// System.out.println("GOT " + e.getKey() + " " + e.getValue());
//		// }
//
//		System.out.println(dict.propose("w", 10));
//
//	}

}
